module dungeonish.main;

// version(build) pragma(export_version, Windowed);
// version(build) pragma(export_version, Debug);

import std.stdio;
import std.math;
import std.string;
import std.file;
import std.conv;
import std.array;

import derelict.util.exception;
import derelict.sdl.sdl;
import derelict.sdl.mixer;
import derelict.opengl.gl;
import derelict.opengl.glu;
import derelict.openal.al;

import nade.random;
import nade.time;
import nade.controller;
import nade.keyboard;
import nade.mouse;
import nade.sound;
import nade.vector;
import nade.region;
import nade.angle;
import nade.color;
import nade.font;
import nade.grid;
import nade.primitive;
import nade.texture;
import nade.hud;
import nade.stack;

import dungeon.player;
import dungeon.character;
import dungeon.inventory;
import dungeon.equipment;
import dungeon.tileset;
import dungeon.map;
import dungeon.mapper;
import dungeon.game;
import dungeon.hud;
import dungeon.console;


enum { GRID_W = 256, GRID_H = 256 }

enum { MAPPER_ITERATIONS = 100 }


int main()
{
	bool done = false;

	try
	{
		initRandom();


		try { DerelictSDL.load(); }
		catch(Exception e) { DerelictSDL.load("Modules\\SDL.dll"); }

		try { DerelictSDLMixer.load(); }
		catch(Exception e) { DerelictSDLMixer.load("Modules\\SDL_mixer.dll"); }

		try { DerelictAL.load(); }
		catch(Exception e) { DerelictAL.load("Modules\\OpenAL32.dll"); }

		DerelictGL.load();
		DerelictGLU.load();


		if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0)
		{
			writefln("Unable to initialize SDL: %s", SDL_GetError());
			SDL_Quit();
			return 1;
		}

		version(Windowed)
			auto screen = SDL_SetVideoMode(1024, 768, 32, SDL_OPENGL);
		else
			auto screen = SDL_SetVideoMode(1024, 768, 32, SDL_OPENGL | SDL_FULLSCREEN);
		if(screen is null)
		{
			writefln("Unable to set 1024x768 video: %s", SDL_GetError());
			SDL_Quit();
			return 1;
		}

		GLVersion glv = DerelictGL.availableVersion();

		DerelictGL.loadExtensions();

		SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
		SDL_EnableUNICODE(SDL_TRUE);


		ALenum error;
		alGetError();

		ALCdevice* alDevice = alcOpenDevice(alcGetString(null, ALC_DEFAULT_DEVICE_SPECIFIER));
		if((error = alGetError()) != AL_NO_ERROR) throw new Error(format("Error: alcOpenDevice: %d", error));

		int[3] cdata = [44100, 0, 1];
		ALCvoid* alContext = alcCreateContext(alDevice, cdata.ptr);
		if((error = alGetError()) != AL_NO_ERROR) throw new Error(format("Error: alcCreateContext: %d", error));

		alcMakeContextCurrent(alContext);
		if((error = alGetError()) != AL_NO_ERROR) throw new Error(format("Error: alcMakeContextCurrent: %d", error));

		alcProcessContext(alContext);
		if((error = alGetError()) != AL_NO_ERROR) throw new Error(format("Error: alcProcessContext: %d", error));

		ALfloat listenerPos[3] = [0.0, 0.0, 0.0];
		ALfloat listenerVel[3] = [0.0, 0.0, 0.0];
		ALfloat listenerOri[6] = [0.0, 0.0, -1.0, 0.0, 1.0, 0.0];

		alListenerfv(AL_POSITION, listenerPos.ptr);
		alListenerfv(AL_VELOCITY, listenerVel.ptr);
		alListenerfv(AL_ORIENTATION, listenerOri.ptr);
		alListenerf(AL_GAIN, 1.0);


		glClearColor(0.0, 0.0, 0.0, 0.5);
		glLineWidth(1);
		glEnable(GL_COLOR_MATERIAL);
		glEnable(GL_LINE_SMOOTH);
		glEnable(GL_POINT_SMOOTH);
		glClearDepth(1.0f);
		glEnable(GL_DEPTH_TEST);
		glDepthFunc(GL_LEQUAL);
		glDisable(GL_CULL_FACE);
		glCullFace(GL_BACK);
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

		glEnable(GL_ALPHA_TEST);

		glEnable(GL_LIGHTING);

		float[4] lightAmb = [0.0f, 0.0f, 0.0f, 1.0f];
		glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lightAmb.ptr);

		float[4] light0Pos = [0.0f, -0.5f, 1.5f, 1.0f];
		float[4] light0Amb = [0.0f, 0.0f, 0.0f, 1.0f];
		float[4] light0Dif = [1.0f, 0.0f, 0.0f, 1.0f];
		float[4] light0Spc = [0.0f, 0.0f, 0.0f, 1.0f];
		glEnable(GL_LIGHT0);
		glLightfv(GL_LIGHT0, GL_POSITION, light0Pos.ptr);
		glLightfv(GL_LIGHT0, GL_AMBIENT, light0Amb.ptr);
		glLightfv(GL_LIGHT0, GL_DIFFUSE, light0Dif.ptr);
		glLightfv(GL_LIGHT0, GL_SPECULAR, light0Spc.ptr);
		glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 0.5f);
		glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0.0f);
		glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 1.0f/9.0f);

		float[4] matAmb = [0.0f, 0.0f, 0.0f, 1.0f];
		float[4] matDif = [1.0f, 1.0f, 1.0f, 1.0f];
		float[4] matEmi = [0.0f, 0.0f, 0.0f, 0.0f];
		float[4] matSpc = [0.5f, 0.5f, 0.5f, 1.0f];
		glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, matAmb.ptr);
		glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, matDif.ptr);
		glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, matEmi.ptr);
		glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, matSpc.ptr);
		glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 15);

		glViewport(0, 0, 1024, 768);


		Vector2 mousePosition = Vector2.ZERO;
		Vector2 mouseScenePosition = Vector2.ZERO;
		Vector2 mouseHUDPosition = Vector2.ZERO;

		font = new Font("Fonts\\font");


		auto game = new Game("AST\\");


		addController(Keyboard.get);
		addController(Mouse.get);


		Vector2i ppos = Vector2i(GRID_W/2, GRID_H/2);
		// Vector2i ppos = Vector2i(random(1, GRID_W - 1), random(1, GRID_H - 1));


		auto map = new Map(game, GRID_W, GRID_H, 1, "wall");
		{
			glDisable(GL_LIGHTING);
			glDisable(GL_DEPTH_TEST);

			glMatrixMode(GL_PROJECTION);
			glLoadIdentity();
			glOrtho(0, 100, 0, 75, -1, 1);

			glMatrixMode(GL_MODELVIEW);
			glLoadIdentity();

			map.setTileType(ppos, map.tileset.getTile("floor"));
			auto mapper = game.getRandomMapper();
			for(int i = 0;  i < MAPPER_ITERATIONS;  ++i)
			{
				mapper.generate(map, map.usedRegion.expand(15), 5000);

				glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
				glColor3f(1, 0, 0);
				glBegin(GL_QUADS);
				glVertex2f(0, 75);
				glVertex2f(i*100.0/MAPPER_ITERATIONS, 75);
				glVertex2f(i*100.0/MAPPER_ITERATIONS, 69);
				glVertex2f(0, 69);
				glEnd();
				font.printCenter(50, 70, "Generating map", 3);
				SDL_GL_SwapBuffers();
			}
		}

		auto player = new Player(map, ppos, game.getRace("human"), game.getKlass("rouge"));
		game.hudCharacter = player.character;
		auto playerDeathSamples = [
			new Sample("Sounds\\death.wav")
		];


		auto hud = new HUD();
		hud.show(game.hud, Region2(0, 0, 1024, 768));
		foreach(window; game.hud.children)
			game.hud.resize(window, window.layout);


		void bindUp() { player.inputDirection = Vector2i(0, 1); }
		void bindRelUp() { if(player.inputDirection == Vector2i(0, 1)) player.inputDirection = Vector2i.ZERO; }
		void bindDown() { player.inputDirection = Vector2i(0, -1); }
		void bindRelDown() { if(player.inputDirection == Vector2i(0, -1)) player.inputDirection = Vector2i.ZERO; }
		void bindLeft() { player.inputDirection = Vector2i(-1, 0); }
		void bindRelLeft() { if(player.inputDirection == Vector2i(-1, 0)) player.inputDirection = Vector2i.ZERO; }
		void bindRight() { player.inputDirection = Vector2i(1, 0); }
		void bindRelRight() { if(player.inputDirection == Vector2i(1, 0)) player.inputDirection = Vector2i.ZERO; }
		Keyboard.bind(SDLK_UP, &bindUp, &bindRelUp);
		Keyboard.bind(SDLK_DOWN, &bindDown, &bindRelDown);
		Keyboard.bind(SDLK_LEFT, &bindLeft, &bindRelLeft);
		Keyboard.bind(SDLK_RIGHT, &bindRight, &bindRelRight);
		Keyboard.bind(SDLK_w, &bindUp, &bindRelUp);
		Keyboard.bind(SDLK_s, &bindDown, &bindRelDown);
		Keyboard.bind(SDLK_a, &bindLeft, &bindRelLeft);
		Keyboard.bind(SDLK_d, &bindRight, &bindRelRight);

		Keyboard.bind(SDLK_BACKQUOTE, {
			game.console.toggle(600);
		});

		Mouse.bind(SDL_BUTTON_LEFT, {
			if(!hud.click(mouseHUDPosition))
				player.addClick(mousePosition*8 + player.position);
		});
		Mouse.bindMove((real x, real y, real dx, real dy) {
			mousePosition = Vector2(x, y);
			mouseHUDPosition = Vector2(512 + x*512, 384 + y*512);
		});


		real playerLight = 0.9;

		while(!done)
		{
			if(updateTime())
			{
				map.iterate();

				if(player.character.stat("HP") <= 0)
				{
					if(!playerDeathSamples.empty)
					{
						auto source = new Source;
						source.position = Vector3(0, 0, 0);
						source.velocity = Vector3(0, 0, 0);
						source.play(playerDeathSamples[random(0, $)]);
					}

					player.teleport(ppos);
					player.character.resetMods;
				}
			}


			const camFrom = Vector2i(cast(int)floor(player.position.x - 8), cast(int)floor(player.position.y - 6));
			const camTo = camFrom + Vector2i(17, 17);


			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

			// Render scene
			{
				glEnable(GL_LIGHTING);
				glEnable(GL_DEPTH_TEST);

				glMatrixMode(GL_PROJECTION);
				glLoadIdentity();
				glFrustum(-0.1, 0.1, -0.075, 0.075, 0.1, 30);

				glMatrixMode(GL_MODELVIEW);
				glLoadIdentity();
				gluLookAt(
					player.position.x, player.position.y, 8,
					player.position.x, player.position.y, 0, 
					0, 1, 0
					);

				light0Pos[0] = player.position.x;
				light0Pos[1] = player.position.y;
				glLightfv(GL_LIGHT0, GL_POSITION, light0Pos.ptr);

				playerLight = playerLight*0.85 + random(0.8, 1)*0.15;
				light0Dif[0] = playerLight;
				light0Dif[1] = playerLight;
				light0Dif[2] = playerLight*0.75;
				glLightfv(GL_LIGHT0, GL_DIFFUSE, light0Dif.ptr);

				map.render(Region2(camFrom, camTo));
			}

			// Render HUD
			{
				glDisable(GL_LIGHTING);
				glDisable(GL_DEPTH_TEST);

				glMatrixMode(GL_PROJECTION);
				glLoadIdentity();
				glOrtho(0, 1024, 0, 768, -1, 1);

				glMatrixMode(GL_MODELVIEW);
				glLoadIdentity();

				hud.render;
				game.console.render;
			}

			SDL_GL_SwapBuffers();


			SDL_Event event;
			while(SDL_PollEvent(&event)  &&  !done)
			{
				bool consumed = false;
				if(game.console.isOpen)
				{
					consumed = game.console.processEvent(event, done);
				}
				else
				{
					foreach(controller; allControllers)
					{
						consumed = controller.processEvent(event);
						if(consumed) break;
					}
				}

				if(!consumed)
				{
					switch(event.type)
					{
						case SDL_KEYDOWN:
						{
							switch(event.key.keysym.sym)
							{
								case SDLK_F4:
								{
									if((event.key.keysym.mod&KMOD_ALT) != 0) done = true;
									break;
								}
								case SDLK_1:
								{
									glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0.0f);
									glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 1.0f/9.0f);
									break;
								}
								case SDLK_2:
								{
									glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0.0f);
									glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 1.0f/36.0f);
									break;
								}
								case SDLK_3:
								{
									glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0.0f);
									glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 1.0f/81.0f);
									break;
								}
								case SDLK_5:
								{
									glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 1.0f/3.0f);
									glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 0.0f);
									break;
								}
								case SDLK_6:
								{
									glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 1.0f/6.0f);
									glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 0.0f);
									break;
								}
								case SDLK_7:
								{
									glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 1.0f/9.0f);
									glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 0.0f);
									break;
								}

								default:
							}
							break;
						}

						case SDL_QUIT:
						{
							done = true;
							break;
						}

						default:
					}
				}
			}

			SDL_Delay(0);
		}
	}
	catch(Exception e)
	{
		writefln("%s", e);
		return 1;
	}

	SDL_Quit();

	return 0;
}
